package ds

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/llakcs/agile-go/log"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/utils"
	"time"
)

type SqlLogOption func(*SqlLogger)

type SqlLogger struct {
	log                       log.Logger
	level                     logger.LogLevel
	IgnoreRecordNotFoundError bool
	SlowThreshold             time.Duration
}

func WithLogLevel(level logger.LogLevel) SqlLogOption {
	return func(opts *SqlLogger) {
		opts.level = level
	}
}

func WithIgnoreRNFE(ignore bool) SqlLogOption {
	return func(opts *SqlLogger) {
		opts.IgnoreRecordNotFoundError = ignore
	}
}

func WithSlowThreshold(d time.Duration) SqlLogOption {
	return func(opts *SqlLogger) {
		opts.SlowThreshold = d
	}
}

func NewSqlLogger(logger log.Logger, opts ...SqlLogOption) *SqlLogger {
	s := &SqlLogger{log: logger}
	for _, o := range opts {
		o(s)
	}
	if s.SlowThreshold == 0 {
		s.SlowThreshold = 200 * time.Millisecond
	}
	return s
}

func (s *SqlLogger) LogMode(level logger.LogLevel) logger.Interface {
	s.level = level
	return s
}

func (s *SqlLogger) Info(_ context.Context, msg string, data ...interface{}) {
	if s.level >= logger.Info {
		s.log.Log(log.LevelInfo, "gorm_log", msg, data)
	}
}

func (s *SqlLogger) Warn(_ context.Context, msg string, data ...interface{}) {
	if s.level >= logger.Warn {
		s.log.Log(log.LevelWarn, "gorm_log", msg, data)
	}
}

func (s *SqlLogger) Error(_ context.Context, msg string, data ...interface{}) {
	if s.level >= logger.Error {
		s.log.Log(log.LevelError, "gorm_log", msg, data)
	}
}
func (s *SqlLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
	if s.level <= logger.Silent {
		return
	}
	elapsed := time.Since(begin)
	switch {
	case err != nil && s.level >= logger.Error && (!errors.Is(err, logger.ErrRecordNotFound) || !s.IgnoreRecordNotFoundError):
		sql, rows := fc()
		if rows == -1 {
			s.log.Log(log.LevelError, "sqlErr", utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			s.log.Log(log.LevelError, "sqlErr", utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	case elapsed > s.SlowThreshold && s.SlowThreshold != 0 && s.level >= logger.Warn:
		sql, rows := fc()
		slowLog := fmt.Sprintf("SLOW SQL >= %v", s.SlowThreshold)
		if rows == -1 {
			s.log.Log(log.LevelWarn, "sqlWarn", utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			s.log.Log(log.LevelWarn, "sqlWarn", utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	case s.level == logger.Info:
		sql, rows := fc()
		if rows == -1 {
			s.log.Log(log.LevelInfo, "sqlInfo", utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			s.log.Log(log.LevelInfo, "sqlInfo", utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	}
}
